1   /*
2    * Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  /*
27   *
28   *  (C) Copyright IBM Corp. 1999 All Rights Reserved.
29   *  Copyright 1997 The Open Group Research Institute.  All rights reserved.
30   */
31  package sun.security.krb5;
32  
33  import java.io.File;
34  import java.io.FileInputStream;
35  import java.util.Hashtable;
36  import java.util.Vector;
37  import java.util.ArrayList;
38  import java.io.BufferedReader;
39  import java.io.InputStreamReader;
40  import java.io.IOException;
41  import java.util.Enumeration;
42  import java.util.StringTokenizer;
43  import java.net.InetAddress;
44  import java.net.UnknownHostException;
45  import java.util.List;
46  import sun.net.dns.ResolverConfiguration;
47  import sun.security.krb5.internal.crypto.EType;
48  import sun.security.krb5.internal.ktab.*;
49  import sun.security.krb5.internal.Krb5;
50  
51  /**
52   * This class maintains key-value pairs of Kerberos configurable constants
53   * from configuration file or from user specified system properties.
54   */
55  
56  public class Config {
57  
58      /*
59       * Only allow a single instance of Config.
60       */
61      private static Config singleton = null;
62  
63      /*
64       * Hashtable used to store configuration infomation.
65       */
66      private Hashtable<String,Object> stanzaTable;
67  
68      private static boolean DEBUG = sun.security.krb5.internal.Krb5.DEBUG;
69  
70      // these are used for hexdecimal calculation.
71      private static final int BASE16_0 = 1;
72      private static final int BASE16_1 = 16;
73      private static final int BASE16_2 = 16 * 16;
74      private static final int BASE16_3 = 16 * 16 * 16;
75  
76      /**
77       * Specified by system properties. Must be both null or non-null.
78       */
79      private final String defaultRealm;
80      private final String defaultKDC;
81  
82      // used for native interface
83      private static native String getWindowsDirectory(boolean isSystem);
84  
85  
86      /**
87       * Gets an instance of Config class. One and only one instance (the
88       * singleton) is returned.
89       *
90       * @exception KrbException if error occurs when constructing a Config
91       * instance. Possible causes would be either of java.security.krb5.realm or
92       * java.security.krb5.kdc not specified, error reading configuration file.
93       */
94      public static synchronized Config getInstance() throws KrbException {
95          if (singleton == null) {
96              singleton = new Config();
97          }
98          return singleton;
99      }
100 
101     /**
102      * Refresh and reload the Configuration. This could involve,
103      * for example reading the Configuration file again or getting
104      * the java.security.krb5.* system properties again.
105      *
106      * @exception KrbException if error occurs when constructing a Config
107      * instance. Possible causes would be either of java.security.krb5.realm or
108      * java.security.krb5.kdc not specified, error reading configuration file.
109      */
110 
111     public static synchronized void refresh() throws KrbException {
112         singleton = new Config();
113         KdcComm.initStatic();
114     }
115 
116 
117     /**
118      * Private constructor - can not be instantiated externally.
119      */
120     private Config() throws KrbException {
121         /*
122          * If either one system property is specified, we throw exception.
123          */
124         String tmp =
125             java.security.AccessController.doPrivileged(
126                 new sun.security.action.GetPropertyAction
127                     ("java.security.krb5.kdc"));
128         if (tmp != null) {
129             // The user can specify a list of kdc hosts separated by ":"
130             defaultKDC = tmp.replace(':', ' ');
131         } else {
132             defaultKDC = null;
133         }
134         defaultRealm =
135             java.security.AccessController.doPrivileged(
136                 new sun.security.action.GetPropertyAction
137                     ("java.security.krb5.realm"));
138         if ((defaultKDC == null && defaultRealm != null) ||
139             (defaultRealm == null && defaultKDC != null)) {
140             throw new KrbException
141                 ("System property java.security.krb5.kdc and " +
142                  "java.security.krb5.realm both must be set or " +
143                  "neither must be set.");
144         }
145 
146         // Always read the Kerberos configuration file
147         try {
148             Vector<String> configFile;
149             configFile = loadConfigFile();
150             stanzaTable = parseStanzaTable(configFile);
151         } catch (IOException ioe) {
152             // No krb5.conf, no problem. We'll use DNS or system property etc.
153         }
154     }
155 
156     /**
157      * Gets the default int value for the specified name.
158      * @param name the name.
159      * @return the default Integer, null is returned if no such name and
160      * value are found in configuration file, or error occurs when parsing
161      * string to integer.
162      */
163     public int getDefaultIntValue(String name) {
164         String result = null;
165         int value = Integer.MIN_VALUE;
166         result = getDefault(name);
167         if (result != null) {
168             try {
169                 value = parseIntValue(result);
170             } catch (NumberFormatException e) {
171                 if (DEBUG) {
172                     System.out.println("Exception in getting value of " +
173                                        name + " " +
174                                        e.getMessage());
175                     System.out.println("Setting " + name +
176                                        " to minimum value");
177                 }
178                 value = Integer.MIN_VALUE;
179             }
180         }
181         return value;
182     }
183 
184     /**
185      * Gets the default int value for the specified name in the specified
186      * section. <br>This method is quicker by using section name as the
187      * search key.
188      * @param name the name.
189      * @param sectio the name string of the section.
190      * @return the default Integer, null is returned if no such name and
191      * value are found in configuration file, or error occurs when parsing
192      * string to integer.
193      */
194     public int getDefaultIntValue(String name, String section) {
195         String result = null;
196         int value = Integer.MIN_VALUE;
197         result = getDefault(name, section);
198         if (result != null) {
199             try {
200                 value = parseIntValue(result);
201             } catch (NumberFormatException e) {
202                 if (DEBUG) {
203                     System.out.println("Exception in getting value of " +
204                                        name +" in section " +
205                                        section + " "  + e.getMessage());
206                     System.out.println("Setting " + name +
207                                        " to minimum value");
208                 }
209                 value = Integer.MIN_VALUE;
210             }
211         }
212         return value;
213     }
214 
215     /**
216      * Gets the default string value for the specified name.
217      * @param name the name.
218      * @return the default value, null is returned if it cannot be found.
219      */
220     public String getDefault(String name) {
221         if (stanzaTable == null) {
222             return null;
223         } else {
224             return getDefault(name, stanzaTable);
225         }
226     }
227 
228     /**
229      * This method does the real job to recursively search through the
230      * stanzaTable.
231      * @param k the key string.
232      * @param t stanzaTable or sub hashtable within it.
233      * @return the value found in config file, returns null if no value
234      * matched with the key is found.
235      */
236     private String getDefault(String k, Hashtable t) {
237         String result = null;
238         String key;
239         if (stanzaTable != null) {
240             for (Enumeration e = t.keys(); e.hasMoreElements(); ) {
241                 key = (String)e.nextElement();
242                 Object ob = t.get(key);
243                 if (ob instanceof Hashtable) {
244                     result = getDefault(k, (Hashtable)ob);
245                     if (result != null) {
246                         return result;
247                     }
248                 } else if (key.equalsIgnoreCase(k)) {
249                     if (ob instanceof String) {
250                         return (String)(t.get(key));
251                     } else if (ob instanceof Vector) {
252                         result = "";
253                         int length = ((Vector)ob).size();
254                         for (int i = 0; i < length; i++) {
255                             if (i == length -1) {
256                                 result +=
257                                     (String)(((Vector)ob).elementAt(i));
258                             } else {
259                                 result +=
260                                     (String)(((Vector)ob).elementAt(i)) + " ";
261                             }
262                         }
263                         return result;
264                     }
265                 }
266             }
267         }
268         return result;
269     }
270 
271     /**
272      * Gets the default string value for the specified name in the
273      * specified section.
274      * <br>This method is quicker by using the section name as the search key.
275      * @param name the name.
276      * @param section the name of the section.
277      * @return the default value, null is returned if it cannot be found.
278      */
279     public String getDefault(String name, String section) {
280         String stanzaName;
281         String result = null;
282         Hashtable subTable;
283 
284         if (stanzaTable != null) {
285             for (Enumeration e = stanzaTable.keys(); e.hasMoreElements(); ) {
286                 stanzaName = (String)e.nextElement();
287                 subTable = (Hashtable)stanzaTable.get(stanzaName);
288                 if (stanzaName.equalsIgnoreCase(section)) {
289                     if (subTable.containsKey(name)) {
290                         return (String)(subTable.get(name));
291                     }
292                 } else if (subTable.containsKey(section)) {
293                     Object ob = subTable.get(section);
294                     if (ob instanceof Hashtable) {
295                         Hashtable temp = (Hashtable)ob;
296                         if (temp.containsKey(name)) {
297                             Object object = temp.get(name);
298                             if (object instanceof Vector) {
299                                 result = "";
300                                 int length = ((Vector)object).size();
301                                 for (int i = 0; i < length; i++) {
302                                     if (i == length - 1)  {
303                                         result +=
304                                         (String)(((Vector)object).elementAt(i));
305                                     } else {
306                                         result +=
307                                         (String)(((Vector)object).elementAt(i))
308                                                 + " ";
309                                     }
310                                 }
311                             } else {
312                                 result = (String)object;
313                             }
314                         }
315                     }
316                 }
317             }
318         }
319         return result;
320     }
321 
322     /**
323      * Gets the default boolean value for the specified name.
324      * @param name the name.
325      * @return the default boolean value, false is returned if it cannot be
326      * found.
327      */
328     public boolean getDefaultBooleanValue(String name) {
329         String val = null;
330         if (stanzaTable == null) {
331             val = null;
332         } else {
333             val = getDefault(name, stanzaTable);
334         }
335         if (val != null && val.equalsIgnoreCase("true")) {
336             return true;
337         } else {
338             return false;
339         }
340     }
341 
342     /**
343      * Gets the default boolean value for the specified name in the
344      * specified section.
345      * <br>This method is quicker by using the section name as the search key.
346      * @param name the name.
347      * @param section the name of the section.
348      * @return the default boolean value, false is returned if it cannot be
349      * found.
350      */
351     public boolean getDefaultBooleanValue(String name, String section) {
352         String val = getDefault(name, section);
353         if (val != null && val.equalsIgnoreCase("true")) {
354             return true;
355         } else {
356             return false;
357         }
358     }
359 
360     /**
361      * Parses a string to an integer. The convertible strings include the
362      * string representations of positive integers, negative integers, and
363      * hex decimal integers.  Valid inputs are, e.g., -1234, +1234,
364      * 0x40000.
365      *
366      * @param input the String to be converted to an Integer.
367      * @return an numeric value represented by the string
368      * @exception NumberFormationException if the String does not contain a
369      * parsable integer.
370      */
371     private int parseIntValue(String input) throws NumberFormatException {
372         int value = 0;
373         if (input.startsWith("+")) {
374             String temp = input.substring(1);
375             return Integer.parseInt(temp);
376         } else if (input.startsWith("0x")) {
377             String temp = input.substring(2);
378             char[] chars = temp.toCharArray();
379             if (chars.length > 8) {
380                 throw new NumberFormatException();
381             } else {
382                 for (int i = 0; i < chars.length; i++) {
383                     int index = chars.length - i - 1;
384                     switch (chars[i]) {
385                     case '0':
386                         value += 0;
387                         break;
388                     case '1':
389                         value += 1 * getBase(index);
390                         break;
391                     case '2':
392                         value += 2 * getBase(index);
393                         break;
394                     case '3':
395                         value += 3 * getBase(index);
396                         break;
397                     case '4':
398                         value += 4 * getBase(index);
399                         break;
400                     case '5':
401                         value += 5 * getBase(index);
402                         break;
403                     case '6':
404                         value += 6 * getBase(index);
405                         break;
406                     case '7':
407                         value += 7 * getBase(index);
408                         break;
409                     case '8':
410                         value += 8 * getBase(index);
411                         break;
412                     case '9':
413                         value += 9 * getBase(index);
414                         break;
415                     case 'a':
416                     case 'A':
417                         value += 10 * getBase(index);
418                         break;
419                     case 'b':
420                     case 'B':
421                         value += 11 * getBase(index);
422                         break;
423                     case 'c':
424                     case 'C':
425                         value += 12 * getBase(index);
426                         break;
427                     case 'd':
428                     case 'D':
429                         value += 13 * getBase(index);
430                         break;
431                     case 'e':
432                     case 'E':
433                         value += 14 * getBase(index);
434                         break;
435                     case 'f':
436                     case 'F':
437                         value += 15 * getBase(index);
438                         break;
439                     default:
440                         throw new NumberFormatException("Invalid numerical format");
441                     }
442                 }
443             }
444             if (value < 0) {
445                 throw new NumberFormatException("Data overflow.");
446             }
447         } else {
448             value = Integer.parseInt(input);
449         }
450         return value;
451     }
452 
453     private int getBase(int i) {
454         int result = 16;
455         switch (i) {
456         case 0:
457             result = BASE16_0;
458             break;
459         case 1:
460             result = BASE16_1;
461             break;
462         case 2:
463             result = BASE16_2;
464             break;
465         case 3:
466             result = BASE16_3;
467             break;
468         default:
469             for (int j = 1; j < i; j++) {
470                 result *= 16;
471             }
472         }
473         return result;
474     }
475 
476     /**
477      * Finds the matching value in the hashtable.
478      */
479     private String find(String key1, String key2) {
480         String result;
481         if ((stanzaTable != null) &&
482             ((result = (String)
483                 (((Hashtable)(stanzaTable.get(key1))).get(key2))) != null)) {
484             return result;
485         } else {
486             return "";
487         }
488     }
489 
490     /**
491      * Reads name/value pairs to the memory from the configuration
492      * file. The default location of the configuration file is in java home
493      * directory.
494      *
495      * Configuration file contains information about the default realm,
496      * ticket parameters, location of the KDC and the admin server for
497      * known realms, etc. The file is divided into sections. Each section
498      * contains one or more name/value pairs with one pair per line. A
499      * typical file would be:
500      * [libdefaults]
501      *          default_realm = EXAMPLE.COM
502      *          default_tgs_enctypes = des-cbc-md5
503      *          default_tkt_enctypes = des-cbc-md5
504      * [realms]
505      *          EXAMPLE.COM = {
506      *                  kdc = kerberos.example.com
507      *                  kdc = kerberos-1.example.com
508      *                  admin_server = kerberos.example.com
509      *                  }
510      *          SAMPLE_COM = {
511      *                  kdc = orange.sample.com
512      *                  admin_server = orange.sample.com
513      *                  }
514      * [domain_realm]
515      *          blue.sample.com = TEST.SAMPLE.COM
516      *          .backup.com     = EXAMPLE.COM
517      */
518     private Vector<String> loadConfigFile() throws IOException {
519         try {
520             final String fileName = getFileName();
521             if (!fileName.equals("")) {
522                 BufferedReader br = new BufferedReader(new InputStreamReader(
523                 java.security.AccessController.doPrivileged(
524                 new java.security.PrivilegedExceptionAction<FileInputStream> () {
525                 public FileInputStream run() throws IOException {
526                     return new FileInputStream(fileName);
527                 }
528                 })));
529                 String Line;
530                 Vector<String> v = new Vector<>();
531                 String previous = null;
532                 while ((Line = br.readLine()) != null) {
533                     // ignore comments and blank line in the configuration file.
534                     // Comments start with #.
535                     if (!(Line.startsWith("#") || Line.trim().isEmpty())) {
536                         String current = Line.trim();
537                         // In practice, a subsection might look like:
538                         //      EXAMPLE.COM =
539                         //      {
540                         //              kdc = kerberos.example.com
541                         //              ...
542                         //      }
543                         // Before parsed into stanza table, it needs to be
544                         // converted into formal style:
545                         //      EXAMPLE.COM = {
546                         //              kdc = kerberos.example.com
547                         //              ...
548                         //      }
549                         //
550                         // So, if a line is "{", adhere to the previous line.
551                         if (current.equals("{")) {
552                             if (previous == null) {
553                                 throw new IOException(
554                                     "Config file should not start with \"{\"");
555                             }
556                             previous += " " + current;
557                         } else {
558                             if (previous != null) {
559                                 v.addElement(previous);
560                             }
561                             previous = current;
562                         }
563                     }
564                 }
565                 if (previous != null) {
566                     v.addElement(previous);
567                 }
568 
569                 br.close();
570                 return v;
571             }
572             return null;
573         } catch (java.security.PrivilegedActionException pe) {
574             throw (IOException)pe.getException();
575         }
576     }
577 
578 
579     /**
580      * Parses stanza names and values from configuration file to
581      * stanzaTable (Hashtable). Hashtable key would be stanza names,
582      * (libdefaults, realms, domain_realms, etc), and the hashtable value
583      * would be another hashtable which contains the key-value pairs under
584      * a stanza name.
585      */
586     private Hashtable<String,Object> parseStanzaTable(Vector<String> v) throws KrbException {
587         if (v == null) {
588             throw new KrbException("I/O error while reading" +
589                         " configuration file.");
590         }
591         Hashtable<String,Object> table = new Hashtable<>();
592         for (int i = 0; i < v.size(); i++) {
593             String line = v.elementAt(i).trim();
594             if (line.equalsIgnoreCase("[realms]")) {
595                 for (int count = i + 1; count < v.size() + 1; count++) {
596                     // find the next stanza name
597                     if ((count == v.size()) ||
598                         (v.elementAt(count).startsWith("["))) {
599                         Hashtable<String,Hashtable<String,Vector<String>>> temp =
600                             new Hashtable<>();
601                         temp = parseRealmField(v, i + 1, count);
602                         table.put("realms", temp);
603                         i = count - 1;
604                         break;
605                     }
606                 }
607             } else if (line.equalsIgnoreCase("[capaths]")) {
608                 for (int count = i + 1; count < v.size() + 1; count++) {
609                     // find the next stanza name
610                     if ((count == v.size()) ||
611                         (v.elementAt(count).startsWith("["))) {
612                         Hashtable<String,Hashtable<String,Vector<String>>> temp =
613                             new Hashtable<>();
614                         temp = parseRealmField(v, i + 1, count);
615                         table.put("capaths", temp);
616                         i = count - 1;
617                         break;
618                     }
619                 }
620             } else if (line.startsWith("[") && line.endsWith("]")) {
621                 String key = line.substring(1, line.length() - 1);
622                 for (int count = i + 1; count < v.size() + 1; count++) {
623                     // find the next stanza name
624                     if ((count == v.size()) ||
625                         (v.elementAt(count).startsWith("["))) {
626                         Hashtable<String,String> temp =
627                             parseField(v, i + 1, count);
628                         table.put(key, temp);
629                         i = count - 1;
630                         break;
631                     }
632                 }
633             }
634         }
635         return table;
636     }
637 
638     /**
639      * Gets the default configuration file name. This method will never
640      * return null.
641      *
642      * If the system property "java.security.krb5.conf" is defined, we'll
643      * use its value, no matter if the file exists or not. Otherwise,
644      * the file will be searched in a list of possible loations in the
645      * following order:
646      *
647      * 1. at Java home lib\security directory with "krb5.conf" name,
648      * 2. at windows directory with the name of "krb5.ini" for Windows,
649      * /etc/krb5/krb5.conf for Solaris, /etc/krb5.conf otherwise.
650      *
651      * Note: When the Terminal Service is started in Windows (from 2003),
652      * there are two kinds of Windows directories: A system one (say,
653      * C:\Windows), and a user-private one (say, C:\Users\Me\Windows).
654      * We will first look for krb5.ini in the user-private one. If not
655      * found, try the system one instead.
656      */
657     private String getFileName() {
658         String name =
659             java.security.AccessController.doPrivileged(
660                                 new sun.security.action.
661                                 GetPropertyAction("java.security.krb5.conf"));
662         if (name == null) {
663             name = java.security.AccessController.doPrivileged(
664                         new sun.security.action.
665                         GetPropertyAction("java.home")) + File.separator +
666                                 "lib" + File.separator + "security" +
667                                 File.separator + "krb5.conf";
668             if (!fileExists(name)) {
669                 name = null;
670                 String osname =
671                         java.security.AccessController.doPrivileged(
672                         new sun.security.action.GetPropertyAction("os.name"));
673                 if (osname.startsWith("Windows")) {
674                     try {
675                         Credentials.ensureLoaded();
676                     } catch (Exception e) {
677                         // ignore exceptions
678                     }
679                     if (Credentials.alreadyLoaded) {
680                         String path = getWindowsDirectory(false);
681                         if (path != null) {
682                             if (path.endsWith("\\")) {
683                                 path = path + "krb5.ini";
684                             } else {
685                                 path = path + "\\krb5.ini";
686                             }
687                             if (fileExists(path)) {
688                                 name = path;
689                             }
690                         }
691                         if (name == null) {
692                             path = getWindowsDirectory(true);
693                             if (path != null) {
694                                 if (path.endsWith("\\")) {
695                                     path = path + "krb5.ini";
696                                 } else {
697                                     path = path + "\\krb5.ini";
698                                 }
699                                 name = path;
700                             }
701                         }
702                     }
703                     if (name == null) {
704                         name = "c:\\winnt\\krb5.ini";
705                     }
706                 } else if (osname.startsWith("SunOS")) {
707                     name =  "/etc/krb5/krb5.conf";
708                 } else {
709                     name =  "/etc/krb5.conf";
710                 }
711             }
712         }
713         if (DEBUG) {
714             System.out.println("Config name: " + name);
715         }
716         return name;
717     }
718 
719     private static String trimmed(String s) {
720         s = s.trim();
721         if (s.charAt(0) == '"' && s.charAt(s.length()-1) == '"' ||
722                 s.charAt(0) == '\'' && s.charAt(s.length()-1) == '\'') {
723             s = s.substring(1, s.length()-1).trim();
724         }
725         return s;
726     }
727     /**
728      * Parses key-value pairs under a stanza name.
729      */
730     private Hashtable<String,String>  parseField(Vector<String> v, int start, int end) {
731         Hashtable<String,String> table = new Hashtable<>();
732         String line;
733         for (int i = start; i < end; i++) {
734             line = v.elementAt(i);
735             for (int j = 0; j < line.length(); j++) {
736                 if (line.charAt(j) == '=') {
737                     String key = (line.substring(0, j)).trim();
738                     String value = trimmed(line.substring(j + 1));
739                     table.put(key, value);
740                     break;
741                 }
742             }
743         }
744         return table;
745     }
746 
747     /**
748      * Parses key-value pairs under [realms].  The key would be the realm
749      * name, the value would be another hashtable which contains
750      * information for the realm given within a pair of braces.
751      */
752     private Hashtable<String,Hashtable<String,Vector<String>>> parseRealmField(Vector<String> v, int start, int end) {
753         Hashtable<String,Hashtable<String,Vector<String>>> table = new Hashtable<>();
754         String line;
755         for (int i = start; i < end; i++) {
756             line = v.elementAt(i).trim();
757             if (line.endsWith("{")) {
758                 String key = "";
759                 for (int j = 0; j < line.length(); j++) {
760                     if (line.charAt(j) == '=') {
761                         key = line.substring(0, j).trim();
762                         // get the key
763                         break;
764                     }
765                 }
766                 for (int k = i + 1; k < end; k++) {
767                     boolean found = false;
768                     line = v.elementAt(k).trim();
769                     for (int l = 0; l < line.length(); l++) {
770                         if (line.charAt(l) == '}') {
771                             found = true;
772                             break;
773                         }
774                     }
775                     if (found == true) {
776                         Hashtable<String,Vector<String>> temp = parseRealmFieldEx(v, i + 1, k);
777                         table.put(key, temp);
778                         i = k;
779                         found = false;
780                         break;
781                     }
782 
783                 }
784             }
785         }
786         return table;
787     }
788 
789     /**
790      * Parses key-value pairs within each braces under [realms].
791      */
792     private Hashtable<String,Vector<String>> parseRealmFieldEx(Vector<String> v, int start, int end) {
793         Hashtable<String,Vector<String>> table = new Hashtable<>();
794         Vector<String> keyVector = new Vector<>();
795         Vector<String> nameVector = new Vector<>();
796         String line = "";
797         String key;
798         for (int i = start; i < end; i++) {
799             line = v.elementAt(i);
800             for (int j = 0; j < line.length(); j++) {
801                 if (line.charAt(j) == '=') {
802                     int index;
803                     key = line.substring(0, j).trim();
804                     if (! exists(key, keyVector)) {
805                         keyVector.addElement(key);
806                         nameVector = new Vector<String> ();
807                     } else {
808                         nameVector = table.get(key);
809                     }
810                     nameVector.addElement(trimmed(line.substring(j + 1)));
811                     table.put(key, nameVector);
812                     break;
813                 }
814             }
815         }
816         return table;
817     }
818 
819     /**
820      * Compares the key with the known keys to see if it exists.
821      */
822     private boolean exists(String key, Vector v) {
823         boolean exists = false;
824         for (int i = 0; i < v.size(); i++) {
825             if (((String)(v.elementAt(i))).equals(key)) {
826                 exists = true;
827             }
828         }
829         return exists;
830     }
831 
832     /**
833      * For testing purpose. This method lists all information being parsed from
834      * the configuration file to the hashtable.
835      */
836     public void listTable() {
837         listTable(stanzaTable);
838     }
839 
840     private void listTable(Hashtable table) {
841         Vector v = new Vector();
842         String key;
843         if (stanzaTable != null) {
844             for (Enumeration e = table.keys(); e.hasMoreElements(); ) {
845                 key = (String)e.nextElement();
846                 Object object = table.get(key);
847                 if (table == stanzaTable) {
848                     System.out.println("[" + key + "]");
849                 }
850                 if (object instanceof Hashtable) {
851                     if (table != stanzaTable)
852                         System.out.println("\t" + key + " = {");
853                     listTable((Hashtable)object);
854                     if (table != stanzaTable)
855                         System.out.println("\t}");
856 
857                 } else if (object instanceof String) {
858                     System.out.println("\t" + key + " = " +
859                                 (String)table.get(key));
860                 } else if (object instanceof Vector) {
861                     v = (Vector)object;
862                     for (int i = 0; i < v.size(); i++) {
863                         System.out.println("\t" + key + " = " +
864                                 (String)v.elementAt(i));
865                     }
866                 }
867             }
868         } else {
869             System.out.println("Configuration file not found.");
870         }
871     }
872 
873     /**
874      * Returns the default encryption types.
875      *
876      */
877     public int[] defaultEtype(String enctypes) {
878         String default_enctypes;
879         default_enctypes = getDefault(enctypes, "libdefaults");
880         String delim = " ";
881         StringTokenizer st;
882         int[] etype;
883         if (default_enctypes == null) {
884             if (DEBUG) {
885                 System.out.println("Using builtin default etypes for " +
886                     enctypes);
887             }
888             etype = EType.getBuiltInDefaults();
889         } else {
890             for (int j = 0; j < default_enctypes.length(); j++) {
891                 if (default_enctypes.substring(j, j + 1).equals(",")) {
892                     // only two delimiters are allowed to use
893                     // according to Kerberos DCE doc.
894                     delim = ",";
895                     break;
896                 }
897             }
898             st = new StringTokenizer(default_enctypes, delim);
899             int len = st.countTokens();
900             ArrayList<Integer> ls = new ArrayList<>(len);
901             int type;
902             for (int i = 0; i < len; i++) {
903                 type = getType(st.nextToken());
904                 if ((type != -1) &&
905                     (EType.isSupported(type))) {
906                     ls.add(type);
907                 }
908             }
909             if (ls.size() == 0) {
910                 if (DEBUG) {
911                     System.out.println(
912                         "no supported default etypes for " + enctypes);
913                 }
914                 return null;
915             } else {
916                 etype = new int[ls.size()];
917                 for (int i = 0; i < etype.length; i++) {
918                     etype[i] = ls.get(i);
919                 }
920             }
921         }
922 
923         if (DEBUG) {
924             System.out.print("default etypes for " + enctypes + ":");
925             for (int i = 0; i < etype.length; i++) {
926                 System.out.print(" " + etype[i]);
927             }
928             System.out.println(".");
929         }
930         return etype;
931     }
932 
933 
934     /**
935      * Get the etype and checksum value for the specified encryption and
936      * checksum type.
937      *
938      */
939     /*
940      * This method converts the string representation of encryption type and
941      * checksum type to int value that can be later used by EType and
942      * Checksum classes.
943      */
944     public int getType(String input) {
945         int result = -1;
946         if (input == null) {
947             return result;
948         }
949         if (input.startsWith("d") || (input.startsWith("D"))) {
950             if (input.equalsIgnoreCase("des-cbc-crc")) {
951                 result = EncryptedData.ETYPE_DES_CBC_CRC;
952             } else if (input.equalsIgnoreCase("des-cbc-md5")) {
953                 result = EncryptedData.ETYPE_DES_CBC_MD5;
954             } else if (input.equalsIgnoreCase("des-mac")) {
955                 result = Checksum.CKSUMTYPE_DES_MAC;
956             } else if (input.equalsIgnoreCase("des-mac-k")) {
957                 result = Checksum.CKSUMTYPE_DES_MAC_K;
958             } else if (input.equalsIgnoreCase("des-cbc-md4")) {
959                 result = EncryptedData.ETYPE_DES_CBC_MD4;
960             } else if (input.equalsIgnoreCase("des3-cbc-sha1") ||
961                 input.equalsIgnoreCase("des3-hmac-sha1") ||
962                 input.equalsIgnoreCase("des3-cbc-sha1-kd") ||
963                 input.equalsIgnoreCase("des3-cbc-hmac-sha1-kd")) {
964                 result = EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD;
965             }
966         } else if (input.startsWith("a") || (input.startsWith("A"))) {
967             // AES
968             if (input.equalsIgnoreCase("aes128-cts") ||
969                 input.equalsIgnoreCase("aes128-cts-hmac-sha1-96")) {
970                 result = EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96;
971             } else if (input.equalsIgnoreCase("aes256-cts") ||
972                 input.equalsIgnoreCase("aes256-cts-hmac-sha1-96")) {
973                 result = EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96;
974             // ARCFOUR-HMAC
975             } else if (input.equalsIgnoreCase("arcfour-hmac") ||
976                    input.equalsIgnoreCase("arcfour-hmac-md5")) {
977                 result = EncryptedData.ETYPE_ARCFOUR_HMAC;
978             }
979         // RC4-HMAC
980         } else if (input.equalsIgnoreCase("rc4-hmac")) {
981             result = EncryptedData.ETYPE_ARCFOUR_HMAC;
982         } else if (input.equalsIgnoreCase("CRC32")) {
983             result = Checksum.CKSUMTYPE_CRC32;
984         } else if (input.startsWith("r") || (input.startsWith("R"))) {
985             if (input.equalsIgnoreCase("rsa-md5")) {
986                 result = Checksum.CKSUMTYPE_RSA_MD5;
987             } else if (input.equalsIgnoreCase("rsa-md5-des")) {
988                 result = Checksum.CKSUMTYPE_RSA_MD5_DES;
989             }
990         } else if (input.equalsIgnoreCase("hmac-sha1-des3-kd")) {
991             result = Checksum.CKSUMTYPE_HMAC_SHA1_DES3_KD;
992         } else if (input.equalsIgnoreCase("hmac-sha1-96-aes128")) {
993             result = Checksum.CKSUMTYPE_HMAC_SHA1_96_AES128;
994         } else if (input.equalsIgnoreCase("hmac-sha1-96-aes256")) {
995             result = Checksum.CKSUMTYPE_HMAC_SHA1_96_AES256;
996         } else if (input.equalsIgnoreCase("hmac-md5-rc4") ||
997                 input.equalsIgnoreCase("hmac-md5-arcfour") ||
998                 input.equalsIgnoreCase("hmac-md5-enc")) {
999             result = Checksum.CKSUMTYPE_HMAC_MD5_ARCFOUR;
1000         } else if (input.equalsIgnoreCase("NULL")) {
1001             result = EncryptedData.ETYPE_NULL;
1002         }
1003 
1004         return result;
1005     }
1006 
1007     /**
1008      * Resets the default kdc realm.
1009      * We do not need to synchronize these methods since assignments are atomic
1010      *
1011      * This method was useless. Kept here in case some class still calls it.
1012      */
1013     public void resetDefaultRealm(String realm) {
1014         if (DEBUG) {
1015             System.out.println(">>> Config try resetting default kdc " + realm);
1016         }
1017     }
1018 
1019     /**
1020      * Check to use addresses in tickets
1021      * use addresses if "no_addresses" or "noaddresses" is set to false
1022      */
1023     public boolean useAddresses() {
1024         boolean useAddr = false;
1025         // use addresses if "no_addresses" is set to false
1026         String value = getDefault("no_addresses", "libdefaults");
1027         useAddr = (value != null && value.equalsIgnoreCase("false"));
1028         if (useAddr == false) {
1029             // use addresses if "noaddresses" is set to false
1030             value = getDefault("noaddresses", "libdefaults");
1031             useAddr = (value != null && value.equalsIgnoreCase("false"));
1032         }
1033         return useAddr;
1034     }
1035 
1036     /**
1037      * Check if need to use DNS to locate Kerberos services
1038      */
1039     public boolean useDNS(String name) {
1040         String value = getDefault(name, "libdefaults");
1041         if (value == null) {
1042             value = getDefault("dns_fallback", "libdefaults");
1043             if ("false".equalsIgnoreCase(value)) {
1044                 return false;
1045             } else {
1046                 return true;
1047             }
1048         } else {
1049             return value.equalsIgnoreCase("true");
1050         }
1051     }
1052 
1053     /**
1054      * Check if need to use DNS to locate the KDC
1055      */
1056     public boolean useDNS_KDC() {
1057         return useDNS("dns_lookup_kdc");
1058     }
1059 
1060     /*
1061      * Check if need to use DNS to locate the Realm
1062      */
1063     public boolean useDNS_Realm() {
1064         return useDNS("dns_lookup_realm");
1065     }
1066 
1067     /**
1068      * Gets default realm.
1069      * @throws KrbException where no realm can be located
1070      * @return the default realm, always non null
1071      */
1072     public String getDefaultRealm() throws KrbException {
1073         if (defaultRealm != null) {
1074             return defaultRealm;
1075         }
1076         Exception cause = null;
1077         String realm = getDefault("default_realm", "libdefaults");
1078         if ((realm == null) && useDNS_Realm()) {
1079             // use DNS to locate Kerberos realm
1080             try {
1081                 realm = getRealmFromDNS();
1082             } catch (KrbException ke) {
1083                 cause = ke;
1084             }
1085         }
1086         if (realm == null) {
1087             realm = java.security.AccessController.doPrivileged(
1088                     new java.security.PrivilegedAction<String>() {
1089                 @Override
1090                 public String run() {
1091                     String osname = System.getProperty("os.name");
1092                     if (osname.startsWith("Windows")) {
1093                         return System.getenv("USERDNSDOMAIN");
1094                     }
1095                     return null;
1096                 }
1097             });
1098         }
1099         if (realm == null) {
1100             KrbException ke = new KrbException("Cannot locate default realm");
1101             if (cause != null) {
1102                 ke.initCause(cause);
1103             }
1104             throw ke;
1105         }
1106         return realm;
1107     }
1108 
1109     /**
1110      * Returns a list of KDC's with each KDC separated by a space
1111      *
1112      * @param realm the realm for which the KDC list is desired
1113      * @throws KrbException if there's no way to find KDC for the realm
1114      * @return the list of KDCs separated by a space, always non null
1115      */
1116     public String getKDCList(String realm) throws KrbException {
1117         if (realm == null) {
1118             realm = getDefaultRealm();
1119         }
1120         if (realm.equalsIgnoreCase(defaultRealm)) {
1121             return defaultKDC;
1122         }
1123         Exception cause = null;
1124         String kdcs = getDefault("kdc", realm);
1125         if ((kdcs == null) && useDNS_KDC()) {
1126             // use DNS to locate KDC
1127             try {
1128                 kdcs = getKDCFromDNS(realm);
1129             } catch (KrbException ke) {
1130                 cause = ke;
1131             }
1132         }
1133         if (kdcs == null) {
1134             kdcs = java.security.AccessController.doPrivileged(
1135                     new java.security.PrivilegedAction<String>() {
1136                 @Override
1137                 public String run() {
1138                     String osname = System.getProperty("os.name");
1139                     if (osname.startsWith("Windows")) {
1140                         String logonServer = System.getenv("LOGONSERVER");
1141                         if (logonServer != null
1142                                 && logonServer.startsWith("\\\\")) {
1143                             logonServer = logonServer.substring(2);
1144                         }
1145                         return logonServer;
1146                     }
1147                     return null;
1148                 }
1149             });
1150         }
1151         if (kdcs == null) {
1152             if (defaultKDC != null) {
1153                 return defaultKDC;
1154             }
1155             KrbException ke = new KrbException("Cannot locate KDC");
1156             if (cause != null) {
1157                 ke.initCause(cause);
1158             }
1159             throw ke;
1160         }
1161         return kdcs;
1162     }
1163 
1164     /**
1165      * Locate Kerberos realm using DNS
1166      *
1167      * @return the Kerberos realm
1168      */
1169     private String getRealmFromDNS() throws KrbException {
1170         // use DNS to locate Kerberos realm
1171         String realm = null;
1172         String hostName = null;
1173         try {
1174             hostName = InetAddress.getLocalHost().getCanonicalHostName();
1175         } catch (UnknownHostException e) {
1176             KrbException ke = new KrbException(Krb5.KRB_ERR_GENERIC,
1177                 "Unable to locate Kerberos realm: " + e.getMessage());
1178             ke.initCause(e);
1179             throw (ke);
1180         }
1181         // get the domain realm mapping from the configuration
1182         String mapRealm = PrincipalName.mapHostToRealm(hostName);
1183         if (mapRealm == null) {
1184             // No match. Try search and/or domain in /etc/resolv.conf
1185             List<String> srchlist = ResolverConfiguration.open().searchlist();
1186             for (String domain: srchlist) {
1187                 realm = checkRealm(domain);
1188                 if (realm != null) {
1189                     break;
1190                 }
1191             }
1192         } else {
1193             realm = checkRealm(mapRealm);
1194         }
1195         if (realm == null) {
1196             throw new KrbException(Krb5.KRB_ERR_GENERIC,
1197                                 "Unable to locate Kerberos realm");
1198         }
1199         return realm;
1200     }
1201 
1202     /**
1203      * Check if the provided realm is the correct realm
1204      * @return the realm if correct, or null otherwise
1205      */
1206     private static String checkRealm(String mapRealm) {
1207         if (DEBUG) {
1208             System.out.println("getRealmFromDNS: trying " + mapRealm);
1209         }
1210         String[] records = null;
1211         String newRealm = mapRealm;
1212         while ((records == null) && (newRealm != null)) {
1213             // locate DNS TXT record
1214             records = KrbServiceLocator.getKerberosService(newRealm);
1215             newRealm = Realm.parseRealmComponent(newRealm);
1216             // if no DNS TXT records found, try again using sub-realm
1217         }
1218         if (records != null) {
1219             for (int i = 0; i < records.length; i++) {
1220                 if (records[i].equalsIgnoreCase(mapRealm)) {
1221                     return records[i];
1222                 }
1223             }
1224         }
1225         return null;
1226     }
1227 
1228     /**
1229      * Locate KDC using DNS
1230      *
1231      * @param realm the realm for which the master KDC is desired
1232      * @return the KDC
1233      */
1234     private String getKDCFromDNS(String realm) throws KrbException {
1235         // use DNS to locate KDC
1236         String kdcs = null;
1237         String[] srvs = null;
1238         // locate DNS SRV record using UDP
1239         if (DEBUG) {
1240             System.out.println("getKDCFromDNS using UDP");
1241         }
1242         srvs = KrbServiceLocator.getKerberosService(realm, "_udp");
1243         if (srvs == null) {
1244             // locate DNS SRV record using TCP
1245             if (DEBUG) {
1246                 System.out.println("getKDCFromDNS using UDP");
1247             }
1248             srvs = KrbServiceLocator.getKerberosService(realm, "_tcp");
1249         }
1250         if (srvs == null) {
1251             // no DNS SRV records
1252             throw new KrbException(Krb5.KRB_ERR_GENERIC,
1253                 "Unable to locate KDC for realm " + realm);
1254         }
1255         for (int i = 0; i < srvs.length; i++) {
1256             String value = srvs[i];
1257             for (int j = 0; j < srvs[i].length(); j++) {
1258                 // filter the KDC name
1259                 if (value.charAt(j) == ':') {
1260                     kdcs = (value.substring(0, j)).trim();
1261                 }
1262             }
1263         }
1264         return kdcs;
1265     }
1266 
1267     private boolean fileExists(String name) {
1268         return java.security.AccessController.doPrivileged(
1269                                 new FileExistsAction(name));
1270     }
1271 
1272     static class FileExistsAction
1273         implements java.security.PrivilegedAction<Boolean> {
1274 
1275         private String fileName;
1276 
1277         public FileExistsAction(String fileName) {
1278             this.fileName = fileName;
1279         }
1280 
1281         public Boolean run() {
1282             return new File(fileName).exists();
1283         }
1284     }
1285 
1286     @Override
1287     public String toString() {
1288         StringBuffer sb = new StringBuffer();
1289         toStringIndented("", stanzaTable, sb);
1290         return sb.toString();
1291     }
1292     private static void toStringIndented(String prefix, Object obj,
1293             StringBuffer sb) {
1294         if (obj instanceof String) {
1295             sb.append(prefix);
1296             sb.append(obj);
1297             sb.append('\n');
1298         } else if (obj instanceof Hashtable) {
1299             Hashtable tab = (Hashtable)obj;
1300             for (Object o: tab.keySet()) {
1301                 sb.append(prefix);
1302                 sb.append(o);
1303                 sb.append(" = {\n");
1304                 toStringIndented(prefix + "    ", tab.get(o), sb);
1305                 sb.append(prefix + "}\n");
1306             }
1307         } else if (obj instanceof Vector) {
1308             Vector v = (Vector)obj;
1309             for (Object o: v.toArray()) {
1310                 toStringIndented(prefix + "    ", o, sb);
1311             }
1312         }
1313     }
1314 }